/*
 * Università degli Studi di Trento
 * Facoltà di Scienze MM.FF.NN.
 * Corso di Laurea Triennale in Informatica
 * A.A. 2010/2011
 * Progetto per il corso di Sistemi Operativi 1
 * Progetto 4: simulazione di una rotonda
 * Autori:
 *  Abram Lorenzo - 140368
 *  Dal Lago Loris - 139428
 *  Florio Alessandro - 140245
 */


/********************************************************************************
  Questo file contiene i metodi per creare, gestire e stampare

  La telecamera utilizza una FIFO per gestire gli eventi. Attraverso la FIFO
  viene passata una copia della car che ha generato l'evento, in tal modo si
  può stampare correttamente la situazione al momento dell'evento, anche se nel
  frattempo la situazione reale e' cambiata.
********************************************************************************/

#include "camera.h"
#include "car.h"

typedef struct SCamera {
  bool prevEntries[4];
  bool prevRoad[Baut_LEN];
  bool prevExits[4];
  int nCarsInQuque[4];
} Camera;

/* struttura per incapsulare gli argomenti da passare alla funzione observe */
struct toObserve_data {
  ptrBaut baut;
  bool interactiveMode;
  bool * willQuitPtr;
  ptrCamera camera;
  pthread_mutex_t * camera_mutex;
};

static void * Camera_observe(void * arg); /* prototipo necessario */

static void Camera_init(ptrBaut baut, bool interactiveMode, bool * willQuitPtr, pthread_mutex_t * camera_mutex, ptrCamera camera){
  int i;
  for (i=0; i<Baut_LEN; i++){
    camera->prevRoad[i] = false;
  }
  for (i=0; i<4; i++){
    camera->prevEntries[i] = false;
    camera->prevExits[i] = false;
    camera->nCarsInQuque[i] = 0;
  }

  struct toObserve_data * arg = new(struct toObserve_data);  
  arg->baut = baut;
  arg->interactiveMode = interactiveMode;
  arg->willQuitPtr = willQuitPtr;
  arg->camera = camera;
  arg->camera_mutex = camera_mutex;

  pthread_t pt;
  int rc = pthread_create(&pt, NULL, Camera_observe, (void *) arg);
  if (rc) throw_error_ends_with_int("Camera_init", "return code from pthread_create() is", rc);
}

ptrCamera Camera_create(ptrBaut baut, bool interactiveMode, bool * willQuitPtr, pthread_mutex_t * camera_mutex){
  ptrCamera camera = new(Camera);
  Camera_init(baut, interactiveMode, willQuitPtr, camera_mutex, camera);
  return camera;
}

static void Camera_updateStatus(ptrCar car, ptrCamera camera){
	 if (car == (ptrCar)NULL)
	    throw_error("Camera_fprintf_log", "Macchina Nulla");

	  int event = Car_getEvent(car);

	  switch(event){
	  case ARRIVAL:
		  if (camera->nCarsInQuque[Car_getStartPoint(car)] == 0)
		  		  camera->prevEntries[Car_getStartPoint(car)] = true;
		  camera->nCarsInQuque[Car_getStartPoint(car)]++;
	    break;
	  case ENTER:
		  camera->nCarsInQuque[Car_getStartPoint(car)]--;
		  if (camera->nCarsInQuque[Car_getStartPoint(car)] == 0)
			  camera->prevEntries[Car_getStartPoint(car)] = false;
		  camera->prevRoad[Car_getStartPoint(car)*3] = true;
	    break;
	  case IN_SECTOR:
		  camera->prevRoad[Car_getPrevPos(car)] = false;
		  camera->prevRoad[Car_getPos(car)] = true;
	    break;
	  case EXIT:
		  camera->prevRoad[(Car_getDestPoint(car)*3+Baut_LEN-1)%Baut_LEN] = false;
		  camera->prevExits[Car_getDestPoint(car)] = true;
	    break;
	  case LEAVING:
		  camera->prevExits[Car_getDestPoint(car)] = false;
	    break;
	  default:
	    throw_error("Camera_update_status", "Evento non gestito!");
	  }
}

static void Camera_fprintf_log(int log, ptrCar car, ptrCamera camera){

  if (car == (ptrCar)NULL)
    throw_error("Camera_fprintf_log", "Macchina Nulla");

  int event = Car_getEvent(car);
  time_t currentTime = time(NULL);
  struct tm * currentLocalTime = localtime(&currentTime);
  char timeBuf[20];
  strftime(timeBuf, 20, "%x %X", currentLocalTime);

  int outputSize = 200;
  char outputBuffer[outputSize];

  switch(event){
  case ARRIVAL: 
    snprintf(outputBuffer, outputSize, "%s AUTO %s ARRIVATA DA %s CON DESTINAZIONE %s\n%c",
             timeBuf, Car_getTag(car), Car_getStartPointStr(car), Car_getDestPointStr(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    break;  
  case ENTER:
    snprintf(outputBuffer, outputSize, "%s AUTO %s HA OCCUPATO SETTORE %d\n%c",
             timeBuf, Car_getTag(car), Car_getPos(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    break;
  case IN_SECTOR:
    snprintf(outputBuffer, outputSize, "%s AUTO %s HA LIBERATO IL SETTORE %d DOPO %ld\n%c",
             timeBuf, Car_getTag(car), Car_getPrevPos(car), Car_getSectStayTime(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    snprintf(outputBuffer, outputSize, "%s AUTO %s HA OCCUPATO SETTORE %d\n%c",
             timeBuf, Car_getTag(car), Car_getPos(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    break;
  case EXIT:
    snprintf(outputBuffer, outputSize, "%s AUTO %s HA LIBERATO IL SETTORE %d DOPO %ld\n%c",
             timeBuf, Car_getTag(car), Car_getPrevPos(car), Car_getSectStayTime(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    snprintf(outputBuffer, outputSize, "%s AUTO %s ARRIVATA A DESTINAZIONE %s\n%c",
             timeBuf, Car_getTag(car), Car_getDestPointStr(car), '\0');
    write(log, outputBuffer, (int)strlen(outputBuffer));
    break;
  case LEAVING:
    /* nothing to print */
    break;
  default:
    throw_error("Camera_fprintf_log", "Evento non gestito!");
  }
}


static char * printLetter(bool car){
  return (car ? "A" : "X");
}

static void Camera_fprintf_scenario(int file, ptrCamera camera){

  int d = 8;
  char lineBuffer[d];

  snprintf(lineBuffer, d, "  %s%s  \n%s",
	   printLetter(camera->prevEntries[2]),   /* entrata Povo */
	   printLetter(camera->prevExits[2]),     /* uscita Povo */
	   "\0");
  write(file, lineBuffer, d);
  char lineBuffer1[d];

  snprintf(lineBuffer1, d, " %s%s%s%s \n%s",
	   printLetter(camera->prevRoad[7]),      /* settore 1 */
	   printLetter(camera->prevRoad[6]),      /* settore 2 */
	   printLetter(camera->prevRoad[5]),      /* settore 3 */
	   printLetter(camera->prevRoad[4]),      /* settore 4 */
	   "\0");
  write(file, lineBuffer1, d);
  char lineBuffer2[d];

  snprintf(lineBuffer2, d, "%s%s  %s%s\n%s",
	   printLetter(camera->prevExits[3]),     /* uscita IRST */
	   printLetter(camera->prevRoad[8]),      /* settore C */
	   printLetter(camera->prevRoad[3]),      /* settore 5 */
	   printLetter(camera->prevEntries[1]),   /* entrata Villazzano */
	   "\0");
  write(file, lineBuffer2, d);
  char lineBuffer3[d];

  snprintf(lineBuffer3, d, "%s%s  %s%s\n%s",
	   printLetter(camera->prevEntries[3]),   /* entrata IRST */
	   printLetter(camera->prevRoad[9]),      /* settore B */
	   printLetter(camera->prevRoad[2]),      /* settore 6 */
	   printLetter(camera->prevExits[1]),     /* uscita Villazzano */
	   "\0");
  write(file, lineBuffer3, d);
  char lineBuffer4[d];

  snprintf(lineBuffer4, d, " %s%s%s%s \n%s",
	   printLetter(camera->prevRoad[10]),     /* settore A */
	   printLetter(camera->prevRoad[11]),     /* settore 9 */
	   printLetter(camera->prevRoad[0]),      /* settore 8 */
	   printLetter(camera->prevRoad[1]),      /* settore 7 */
	   "\0");
  write(file, lineBuffer4, d);
  char lineBuffer5[d];

  snprintf(lineBuffer5, d, "  %s%s \n\n%s",
	   printLetter(camera->prevExits[0]),     /* uscita Mesiano */
	   printLetter(camera->prevEntries[0]),   /* entrata Mesiano */
	   "\0");
  write(file, lineBuffer5, d);

}



/* Thread che gestisce il comportamento della telecamera */
static void * Camera_observe(void * arg){

  struct toObserve_data * data = (struct toObserve_data *) arg;
  int logFileFD = creat(logFile_name, O_WRONLY | O_NONBLOCK | O_CREAT | FILE_MODE);
  int streamingFileFD;
  if (data->interactiveMode)
	  streamingFileFD = creat(streamingFile_name, O_WRONLY | O_NONBLOCK | O_CREAT | FILE_MODE);
  else streamingFileFD = 1;

#ifdef DEBUG_CAMERA
  streamingFileFD = 1;
#endif

  while(!*data->willQuitPtr){
    ptrCar car;
        if (read(Baut_getReadFifo(data->baut), &car, sizeof(car)) > 0){
			if(car == (ptrCar)NULL){
				throw_error("Camera_observe", "Car nulla!");
			}
			else {
				Camera_updateStatus(car, data->camera);
				Camera_fprintf_log(logFileFD, car, data->camera);
				Camera_fprintf_scenario(streamingFileFD, data->camera);
				Car_destroy(car);
			}
        }
  }


  if (close(logFileFD) == -1) throw_error_ends_with_str("Camera_observe", "errore nella chiusura del file", logFile_name);

  if (data->interactiveMode) {
    if (close(streamingFileFD) == -1) throw_error_ends_with_str("Camera_observe", "errore nella chiusura del file", streamingFile_name);
  }
  pthread_mutex_unlock(data->camera_mutex);
  pthread_exit(NULL);
}

static void Camera_deinit(ptrCamera camera){
  /* no pointers to deinit */
}

void Camera_destroy(ptrCamera camera){
  if (camera != NULL){
    Camera_deinit(camera);
    delete(camera);
  }
}
